#include "common.h"
#include "utils/systime.h"

#include "VArbiter.h"
#include "VArbiter___024root.h"
#include "verilated_vcd_c.h"
#define TEST_CNT 200000
#define TRACE 1
#define MEM_SIZE 0x100000
#define MEM_MBASE 0x80000000ull
#define CLINT_BASE 0x02000000ull
#define CODE_SIZE (MEM_SIZE/2)

#define CACHE_SIZE (4*1024)
#undef WAVE_MAX_CNT
#define WAVE_MAX_CNT 1000
#undef SIM_MAX_CYCLES
#define SIM_MAX_CYCLES (TEST_CNT * 10)


void simcycle(uint32_t n);
void reset();
void dump_wave();
void init_mem();
void Arbiter_test();
void Sel_test();
void init_op();

uint64_t mem[MEM_SIZE / sizeof(uint64_t)] = {0};
VArbiter *top;
VerilatedVcdC *tfp;
uint32_t total_cycles = 0;
bool trace = false;

int main(int argc, char **argv, char **env)
{
    uint64_t start_us,end_us;
    top = new VArbiter;
#if TRACE
    tfp = new VerilatedVcdC; // 初始化VCD对象指针
    Verilated::traceEverOn(true); // 打开追踪功能
    top->trace(tfp, 0);
    tfp->open("wave_Arbiter.vcd"); // 打开vcd
    trace = true;
#endif
    srand(0);
    reset();
    init_mem();
    init_op();
    printf(ANSI_FMT("sim start",ANSI_FG_BLUE) "\n");
    start_us = SYS_GetTicks();
    Arbiter_test();
    // Sel_test();
    end_us = SYS_GetTicks();
    printf(ANSI_FMT("sim stop",ANSI_FG_BLUE) "\n");
    uint64_t used_ms = (end_us - start_us)/1000;
    printf("used ms is %ld sim cycles:%d\n",used_ms,total_cycles);
    if(used_ms)
        printf("speed %.0fcycles/s\n",total_cycles*1000.0f/used_ms);
    
#if TRACE
    tfp->close();
    delete tfp;
#endif
    delete top;
    return 0;
}
typedef enum {
    LOAD = 0,
    STORE,
    _LS_TYPE_MAX,
} LS_TYPE;
typedef enum {
    ICACHE,
    DCACHE
} CACHE;
typedef struct
{
    uint64_t addr;
    uint8_t size;
    uint64_t data;
    LS_TYPE type;
    CACHE cache;
    int cnt;
} LS_OP;
LS_OP icache_op[TEST_CNT];
LS_OP dcache_op[TEST_CNT];
bool check_op(LS_OP op);
bool update_iop();
bool update_dop();

int iop_cnt = 1071540-100
,dop_cnt = 1071540-100
;
void Arbiter_test()
{
    while (iop_cnt < TEST_CNT || dop_cnt < TEST_CNT)
    {
        // if(dop_cnt == 1174080) trace = true;
        if(!update_iop()) break;
        // iop_cnt = TEST_CNT;
        if(!update_dop()) break;
        // dop_cnt = TEST_CNT;
        simcycle(1);
    }

    if(iop_cnt < TEST_CNT || dop_cnt < TEST_CNT)
    {
        printf(ANSI_FMT("Fail",ANSI_FG_RED)"\n");
    }
    else
    {
        printf(ANSI_FMT("Success",ANSI_FG_GREEN)"\n");
    }
}

uint8_t msip = 0;
enum {
 CLINT_BASE_ADDR = 0x02000000,
 CLINT_MSIP_ADDR = CLINT_BASE_ADDR+0x0,
 CLINT_MTIMECMP_ADDR = CLINT_BASE_ADDR+0x4000,
 CLINT_MTIME_ADDR = CLINT_BASE_ADDR+0xBFF8,
};
uint64_t load(uint64_t addr)
{
    while(top->dcache_rbusy) simcycle(1);
    top->dcache_ren = 1;
    top->dcache_raddr = addr;
    top->dcache_rsize = 8;
    simcycle(1);
    top->dcache_ren = 0;
    while (top->dcache_rdata_valid == 0) simcycle(1);
    return top->dcache_rdata;
}
void store(uint64_t addr, uint64_t data)
{
    while(top->dcache_wbusy) simcycle(1);
    top->dcache_wen = 1;
    top->dcache_waddr = addr;
    top->dcache_wsize = 8;
    top->dcache_wdata = data;
    simcycle(1);
    top->dcache_wen = 0;
    
    
}
void Sel_test()
{
    top->icache_ren = 0;
    top->icache_wen = 0;
    top->dcache_ren = 0;
    top->dcache_wen = 0;
    top->dcache_direct = 1;
    int n = 100;
    while (n--)
    {
        if(top->msip_valid_o) msip = top->msip_o;
        top->msip_i = msip;
        uint64_t mtime = load(CLINT_MTIME_ADDR);
        printf("mtime:   %d\n",mtime);
        uint64_t mtimecmp = load(CLINT_MTIMECMP_ADDR);
        printf("mtimecmp:%d\n",mtimecmp);
        uint64_t msip = load(CLINT_MSIP_ADDR);
        // printf("msip:    %d\n",msip);
        // printf("timeintr:%d\n",top->tim_int_req);
        if(top->tim_int_req)
        {
            store(CLINT_MTIMECMP_ADDR,mtimecmp+100);
        }
    }



}

bool update_iop()
{
    if(iop_cnt >= TEST_CNT) return true;
    LS_OP iop = icache_op[iop_cnt];
    static bool iop_send = false;    
    if(iop.type == STORE)
    {
        top->icache_ren = 0;
        if(!iop_send)
        {
            // uint32_t num = rand()%4;
            // if(num != 0) return true;
            if(top->icache_wbusy == 0 && top->icache_wen)
            {
                iop_send = true;
            } else {
                top->icache_wen = 1;
                top->icache_waddr = iop.addr;
                top->icache_wdata = iop.data;
                top->icache_wsize = iop.size;
            }
        }
        else if(top->icache_wbusy == 0)
        {
            top->icache_wen = 0;
            iop_send = false;
            bool success = check_op(iop);
            if(!success) return false;
            // printf("iop_cnt:%d\n",iop_cnt);
            iop_cnt++;
        }
    }
    else
    {
        static LS_OP last_op;
        top->icache_wen = 0;
        if(top->icache_rdata_valid)
        {
            last_op.data = top->icache_rdata;
            bool success = check_op(last_op);
            if(!success) return false;

        }
        if(top->icache_rbusy == 0)
        {
            top->icache_ren = 1;
            top->icache_raddr = iop.addr;
            top->icache_rsize = iop.size;
            last_op = iop;
            // printf("iop_cnt:%d\n",iop_cnt);
            iop_cnt++;
        }
    }
    return true;
}

bool update_dop()
{
    if(dop_cnt >= TEST_CNT) return true;
    LS_OP dop = dcache_op[dop_cnt];
    static bool dop_send = false;    
    if(dop.type == STORE)
    {
        top->dcache_ren = 0;
        if(!dop_send)
        {
            // uint32_t num = rand()%4;
            // if(num != 0) return true;
            if(top->dcache_wbusy == 0 && top->dcache_wen)
            {
                dop_send = true;
            } else {
                top->dcache_wen = 1;
                top->dcache_waddr = dop.addr;
                top->dcache_wdata = dop.data;
                top->dcache_wsize = dop.size;
            }
        }
        else if(top->dcache_wbusy == 0)
        {
            top->dcache_wen = 0;
            dop_send = false;
            bool success = check_op(dop);
            if(!success) return false;
            // printf("dop_cnt:%d\n",dop_cnt);
            dop_cnt++;
        }
    }
    else
    {
        static LS_OP last_op;
        top->dcache_wen = 0;
        if(top->dcache_rdata_valid)
        {
            last_op.data = top->dcache_rdata;
            bool success = check_op(last_op);
            if(!success) return false;

        }
        if(top->dcache_rbusy == 0)
        {
            top->dcache_ren = 1;
            top->dcache_raddr = dop.addr;
            top->dcache_rsize = dop.size;
            last_op = dop;
            // printf("dop_cnt:%d\n",dop_cnt);
            dop_cnt++;
        }
    }
    
    return true;
}


bool check_op(LS_OP op)
{
    uintptr_t addr = (op.addr-MEM_MBASE)/op.size;
    uint64_t data;
    switch (op.size)
    {
    case 1:
        data = ((uint8_t*)mem)[addr];
        break;
    case 2:
        data = ((uint16_t*)mem)[addr];
        break;
    case 4:
        data = ((uint32_t*)mem)[addr];
        break;
    case 8:
        data = ((uint64_t*)mem)[addr];
        break;
    default:
        assert(0);
        break;
    }
    if(data == op.data) return true;
    printf(ANSI_FMT("OP %d: addr: 0x%016lx data: 0x%016lx mem: 0x%016lx size:0x%x %s %s",ANSI_FG_RED) "\n",op.cnt,op.addr,op.data,data,op.size,op.cache==ICACHE?"ICAHCE":"DCACHE",op.type==LOAD?"LOAD":"STORE");
    return false;
}
LS_OP rand_op()
{
    LS_OP op = {0};
    uint64_t addr = rand();
    addr %= MEM_SIZE;
    uint8_t size = rand();
    size %= 3;
    size =  1 << size;
    addr = addr-(addr%size);
    LS_TYPE type = (LS_TYPE)(rand()%_LS_TYPE_MAX); 
    
    // type = STORE;
    
    op.addr = addr;
    op.size = size;
    op.type = type;
    if(type == STORE)
    {
        uint32_t data1 = rand();
        uint32_t data2 = rand();
        uint64_t data = ((uint64_t)data1 << 32) | (uint64_t)data2;
        switch (op.size)
        {
        case 1:
            data &= 0xFFull;
            break;
        case 2:
            data &= 0xFFFFull;
            break;
        case 4:
            data &= 0xFFFFFFFFull;
            break;
        case 8:
            break;
        default:
            assert(0);
            break;
        }
        op.data = data;
    }
    return op;
}
void init_op()
{
    for (int i = 0; i < TEST_CNT; i++)
    {
        dcache_op[i] = rand_op();
        dcache_op[i].cache = DCACHE;
        dcache_op[i].cnt = i;
        if(dcache_op[i].type == STORE && dcache_op[i].addr < CODE_SIZE)
        {
            dcache_op[i].addr += CODE_SIZE;
        }
        dcache_op[i].addr += MEM_MBASE;
        
        icache_op[i] = rand_op();
        icache_op[i].cache = ICACHE;
        icache_op[i].cnt = i;
        icache_op[i].type = LOAD;
        icache_op[i].addr %= CODE_SIZE;
        icache_op[i].addr += MEM_MBASE;
    }
}

void dump_wave()
{
#if TRACE
if(!trace) return;
    static uint32_t wave_cnt = 0;
    if (wave_cnt > WAVE_MAX_CNT)
        return;
    tfp->dump(wave_cnt++);
#endif
}
void quit(int t)
{
#if TRACE
    tfp->close();
    delete tfp;
#endif
    delete top;
    exit(t);
}
void simcycle(uint32_t n)
{
    total_cycles += n;

    while (n--)
    {
        top->clk = 0;
        top->eval();
        dump_wave();
        top->clk = 1;
        top->eval();
        dump_wave();
    }
    if (total_cycles > SIM_MAX_CYCLES)
        quit(-1);
}
void reset()
{
    top->clk = 0;
    top->rst = 1;

    top->eval();
    simcycle(10);
    top->rst = 0;
}
#ifdef __cplusplus
extern "C"
{
#endif

    // DPI IMPORTS
    // DPI import at /home/qingchen/Project/ysyx-workbench/npc/vsrc/Cache/axi_mem.v:1:33
    long long mem_read(long long raddr, svBit is_icache)
    {
        if(raddr < MEM_MBASE || raddr >= MEM_MBASE+MEM_SIZE)
        {
            printf("raddr:0x%016lx out of bond\n",raddr);
            quit(1);
        }
        raddr -= MEM_MBASE;
        return mem[raddr / sizeof(uint64_t)];
    }
    // DPI import at /home/qingchen/Project/ysyx-workbench/npc/vsrc/Cache/axi_mem.v:2:30
    void mem_write(long long waddr, long long wdata, char wmask)
    {
        if(waddr < MEM_MBASE || waddr >= MEM_MBASE+MEM_SIZE)
        {
            printf("waddr:0x%016lx out of bond\n",waddr);
            quit(1);
        }
        waddr -= MEM_MBASE;
        uint64_t old = mem[waddr / sizeof(uint64_t)];
        uint64_t temp = 0;
        for (int i = 0; i < sizeof(uint64_t); i++)
        {
            if (BITS(wmask, i, i))
            {
                temp |= BITS(wdata, i * 8 + 7, i * 8) << i * 8;
            }
            else
            {
                temp |= BITS(old, i * 8 + 7, i * 8) << i * 8;
            }
        }
        mem[waddr / sizeof(uint64_t)] = temp;
        // printf("mem_write: addr: 0x%016lx data:0x%016lx size:0x%02x mem:0x%016lx to 0x%016lx\n",waddr,wdata,(uint8_t)wmask,old,temp);
    }
    // DPI import at /home/qingchen/Project/ysyx-workbench/npc/vsrc/top.v:1:33
    long long pmem_read(long long raddr, char rsize, char is_icache) { return -1; }
    // DPI import at /home/qingchen/Project/ysyx-workbench/npc/vsrc/top.v:2:30
    void pmem_write(long long waddr, long long wdata, char wmask) {}
    // DPI import at /home/qingchen/Project/ysyx-workbench/npc/vsrc/sim/difftest.v:2:30
    void raise_hard_intr() {}
    // DPI import at /home/qingchen/Project/ysyx-workbench/npc/vsrc/sim/difftest.v:1:30
    void submit_inst(long long pc, int inst, long long next_pc) {}

    void difftest_memory_access(long long addr) {}

#ifdef __cplusplus
}
void init_mem()
{
    for (size_t i = 0; i < MEM_SIZE / sizeof(uint64_t); i++)
    {
        mem[i] = rand();
        // mem[i] = ~0ull;
    }
}
#endif